﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using BLL.Extensions.Filtering;

namespace BLL.Extensions
{
    /// <summary>
    /// Contains extension methods for <see cref="IEnumerable"/>
    /// </summary>
    public static class EnumerableExtensions
    {
        /// <summary>
        /// Applies function to all elements of the collection.
        /// </summary>
        /// <typeparam name="T">Type of elements in the collection.</typeparam>
        /// <param name="source">Collection to apply action to.</param>
        /// <param name="action">The action to apply.</param>
        public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
        {
            foreach (T element in source)
                action(element);
        }


        /// <summary>
        /// Adds filter criteria to the IQueryable based on the properties of the filter of type T.
        /// For a filter to be applied, the field has to be decorated with <see
        /// cref="SearchFilterAttribute"/> and the value passed in should not be null.
        /// </summary>
        /// <typeparam name="T">Type of the objects to filter to.</typeparam>
        /// <param name="enumerable">Enumerable instance to extend.</param>
        /// <param name="filter">Object whose properties will be treated as filter expressions.</param>
        public static IQueryable<T> ApplyFilter<T>(this IEnumerable<T> enumerable, object filter)
        {
            var queryable = enumerable.AsQueryable();
            var properties = TypeDescriptor.GetProperties(filter, new[] { new SearchFilterAttribute() });
            if (properties.Count < 1)
                return queryable;

            foreach (PropertyDescriptor property in properties)
            {
                object propertyValue = property.GetValue(filter);
                if (null == propertyValue)
                    continue;

                FilterTypes filterType =
                    ((SearchFilterAttribute)property.Attributes[typeof(SearchFilterAttribute)]).FilterType;

                LambdaExpression predicate;
                var param = Expression.Parameter(typeof(T), property.Name);
                var right = Expression.Constant(propertyValue);
                var left = Expression.Property(param, property.Name);

                if (filterType == FilterTypes.Contains)
                {
                    var methodContains = typeof(string).GetMethod("Contains", new[] { typeof(string) });
                    var filterContains = Expression.Call(left, methodContains, right);
                    predicate = Expression.Lambda(filterContains, param);
                }
                else
                {
                    var expr = Expression.Equal(left, right);
                    predicate = Expression.Lambda(expr, param);
                }

                var expression = Expression.Call(typeof(Queryable),
                                                 "Where",
                                                 new[] { queryable.ElementType },
                                                 queryable.Expression,
                                                 predicate);
                queryable = queryable.Provider.CreateQuery<T>(expression);
            }

            return queryable;
        }
    }
}